Ruby 日記 43日目: Fiberの中断・再開とFiber.yieldの戻り値
次のプログラムを実行すると何が表示されますか
code:gold/ex43/main.rb
f = Fiber.new do |total|
Fiber.yield total + 10
end
p f.resume(100) + f.resume(5)
選択肢:
110と表示される
105と表示される
15と表示される
115と表示される
解説:
Fiber.yield で親のコンテキストに切り替わり、f.resumeで子のコンテキストに切り替わる、って話だった。
resumeメソッドの引数は?
resume(*arg = nil) -> object
自身が表すファイバーへコンテキストを切り替えます。 自身は resume を呼んだファイバーの子となります。
いまいちピンとこない
[PARAM] arg:
self が表すファイバーに渡したいオブジェクトを指定します。
ふむ
[RETURN]
コンテキストの切り替えの際に Fiber.yield に与えられた引数 を返します。ブロックの終了まで実行した場合はブロックの評価結果 を返します。
与えられたブロックとともにファイバーを生成して返します。
ブロックは Fiber#resume に与えられた引数をその引数として実行されます。
なるほど、Fiber#resume の引数 arg は、Fiber.new に渡すブロックの引数になるのね。
だから今回の問題だと、f.resume(100) や f.resume(5) の 100 や 5 が、 Fiber.new do |total| の total に渡されるってことだよね
一旦ここまでの知識をもとに問題を解読すると:
1. f.resume(100) が実行されて Fiber.new のブロックに引数 100 が渡される
2. ブロック内の Fiber.yield(100 + 10) で処理が中断され、親のコンテキストに切り替わる。
3. この時、f.resume(100) の戻り値は110である。
Fiber#resume の コンテキストの切り替えの際に Fiber.yield に与えられた引数 を返します の通り
この後、f.resume(5) されるとどうなるの?って話だね。
今はブロック内は Fiber.yield(100 + 10) で処理が中断された状態なので、resume メソッドが呼ばれるとここから処理が再開することになる。「再開する」といいつつ、これ以降の処理はない。つまりブロックの終了まで処理が実行されたってことだね。
改めて Fiber#resume の戻り値の定義を見てみると
[RETURN]
コンテキストの切り替えの際に Fiber.yield に与えられた引数 を返します。ブロックの終了まで実行した場合はブロックの評価結果 を返します。
後半部分(太字にした)の通り、戻り値は「ブロックの評価結果」になる。
つまりそれは f.resume(5) における Fiber.yield(100 + 10) 自体の評価結果だね。
yield(*arg = nil) -> object
現在のファイバーの親にコンテキストを切り替えます。
コンテキストの切り替えの際に Fiber#resume に与えられた引数を yield メソッドは返します。
Fiber.yield の戻り値は Fiber#resume に与えられた引数 なんだね!
f.resume(5) をしたので、戻り値は 5 だ!
続きを解読すると:
4. f.resume(5) が実行されて Fiber.new のブロックに引数 5 が渡される
5. ブロック内の Fiber.yield(100 + 10) から処理が再開される
6. 後続の処理がないため、f.resume(5)の戻り値は Fiber.yield(100 + 10) の評価結果である
7. Fiber.yield(100 + 10) の評価結果はresumeメソッドに与えられた引数となるので、5である
従って f.resume(100) は 110 を f.resume(5) は 5 を返すため、正解は「115と表示される」だ!
code:sh
# ruby gold/ex43/main.rb
115
/icons/hr.icon
今回の問題で、もう一度 Fiber#resume を呼んだらどうなる?
code:gold/ex43/sample.rb
f = Fiber.new do |total|
Fiber.yield total + 10
end
p f.resume(100) + f.resume(5) + f.resume(1)
code:sh
# ruby gold/ex43/sample.rb
gold/ex43/sample.rb:5:in `resume': dead fiber called (FiberError)
from gold/ex43/sample.rb:5:in `<main>'
エラーになる。
なぜなら、Fiber.new のブロックの処理が最後まで完了している(中断している場所 = 再開する場所 がない)から。